#include <pthread.h>
#include <semaphore.h>
#include <ncurses.h>
#include <stdlib.h>
#include <unistd.h>
#include <ncurses.h>

#include "lightswitch.h"

#define MAX 100
#define SIZE 5
#define TIME 4000000
#define MAX_ARRIVE_TIME 4000000

void print_init();
void create(unsigned long int id, unsigned char type);
void stages(unsigned long int id, unsigned char type);
void print_wait();

sem_t size;
lightswitch_t switcher;

void* employer(void* v) {
    unsigned long int id = ((unsigned long int) v) >> 1;
    unsigned char type = ((unsigned long int) v) & 1;
    create(id, type);

    //Turnos
    lightswitch_lock(&switcher, type);

    //Capacidade
    sem_wait(&size);

    //Travessia
    stages(id, type);

    //Liberando Capacidade
    sem_post(&size);

    //Liberando Turno
    lightswitch_unlock(&switcher);

    return NULL;
}

void* god(void *v) {
    pthread_t *thr;
    unsigned long int id = 1;
    for (;; id++) {
        unsigned long int idd;
        unsigned long int rand = random() % MAX_ARRIVE_TIME;
        usleep(rand);
        idd = (id << 1) | (rand & 1);
        thr = calloc(1, sizeof(pthread_t));
        pthread_create(thr, NULL, employer, (void*) idd);
    }
}


int main() {

    print_init();

    lightswitch_init(&switcher);
    sem_init(&size, 0, SIZE);

    {
        pthread_t thr;
        pthread_create(&thr, NULL, god, NULL);
    }

    print_wait();

    exit(0);
}

#define ROPE_ROW 5
#define LAND 25
#define LOG 10
#define rope_x_start LAND
unsigned long int rope_x_end;
unsigned long int rope_x_lenght;
unsigned long int rope_x_time;
volatile unsigned long int rope_pos[SIZE];
volatile unsigned long int rope_count;
pthread_mutex_t rope_spacer;
volatile unsigned long int wait_count[2];
pthread_mutex_t output_mutex;
WINDOW *output_log_win;

void print_erase(int c) {
    int r = ROPE_ROW -2;
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    r++;
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
    mvprintw(r++, c, "                        ");
}

void print_left(int c, unsigned long int id) {
    int r = ROPE_ROW - 2;
    mvprintw(r++, c, "       _%ld          ", id);
    mvprintw(r++, c, "      (\\\\,              ");
    r++;
    mvprintw(r++, c, "       \\ (              ");
    mvprintw(r++, c, "        \\ \\             ");
    mvprintw(r++, c, "         \\ \\,^^,_       ");
    mvprintw(r++, c, "          \\_<66,G)      ");
    mvprintw(r++, c, "      _    (_,_/\\_      ");
    mvprintw(r++, c, "     ( ',  /    _ \\     ");
    mvprintw(r++, c, "    /_,  \\/    /*\\ \\    ");
    mvprintw(r++, c, "Oo./_) \\ ( e  >   \\,)__ ");
    mvprintw(r++, c, " \\_/    \\_,-   )   \\ _/ ");
    mvprintw(r++, c, "         (_ ,_/     U   ");
    mvprintw(r++, c, "          \\  )          ");
    mvprintw(r++, c, "           \\>           ");
    mvprintw(r++, c, "          Oo.)          ");
}

void print_right(int c, unsigned long int id) {
    int r = ROPE_ROW - 2;
    mvprintw(r++, c, "         %6ld _       ", id);
    mvprintw(r++, c, "              ,//)      ");
    r++;
    mvprintw(r++, c, "              ) /       ");
    mvprintw(r++, c, "             / /        ");
    mvprintw(r++, c, "       _,^^,/ /         ");
    mvprintw(r++, c, "      (G,66<_/          ");
    mvprintw(r++, c, "      _/\\_,_)    _      ");
    mvprintw(r++, c, "     / _    \\  ,' )     ");
    mvprintw(r++, c, "    / /*\\    \\/  ,_\\    ");
    mvprintw(r++, c, " __(,/   >  e ) / (_\\.oO");
    mvprintw(r++, c, " \\_ /   (   -,_/    \\_/ ");
    mvprintw(r++, c, "   U     \\_, _)         ");
    mvprintw(r++, c, "          (  /          ");
    mvprintw(r++, c, "           >/           ");
    mvprintw(r++, c, "          (.oO          ");
}

void print_left_counter(){
    int row,col;
    int r, c;
    getmaxyx(stdscr,row,col);
    r = row - 25;
    c = (LAND - 12) / 2;
    mvprintw(r++, c, "+----------+");
    mvprintw(r++, c, "|  %6ld  |",wait_count[0]);
    mvprintw(r++, c, "+----------+");
}

void print_right_counter(){
    int row,col;
    int r, c;
    getmaxyx(stdscr,row,col);
    r = row - 25;
    c = col - LAND + (LAND - 12) / 2 + 1;
    mvprintw(r++, c, "+----------+");
    mvprintw(r++, c, "|  %6ld  |",wait_count[1]);
    mvprintw(r++, c, "+----------+");
}

void create(unsigned long int id, unsigned char type) {
    pthread_mutex_lock(&output_mutex);
    wscrl(output_log_win, 1);
    if(type) {
        wait_count[type]++;
        print_left_counter();
        refresh();
        mvwprintw(output_log_win, LOG-1, 1, "%6ld left->right created\t( %ld waiting )", id, wait_count[type]);
    } else {
        wait_count[type]++;
        print_right_counter();
        refresh();
        mvwprintw(output_log_win, LOG-1, 1, "%6ld right->left created\t( %ld waiting )", id, wait_count[type]);
    }
    wrefresh(output_log_win);
    pthread_mutex_unlock(&output_mutex);
}

void stages(unsigned long int id, unsigned char type) {
    unsigned long int stage;
    unsigned long int i;

    if(type) {
        // Start
        pthread_mutex_lock(&output_mutex);
        rope_count++;
        wait_count[type]--;
        print_left_counter();
        refresh();

        wscrl(output_log_win, 1);
        mvwprintw(output_log_win, LOG-1, 1,"%6d left->right start\t( %ld in rope )", id, rope_count);
        wrefresh(output_log_win);
        pthread_mutex_unlock(&output_mutex);

        // Transation
        pthread_mutex_lock(&rope_spacer);
        for(i=0, stage=rope_x_start; stage<=rope_x_end; i++, stage++){
            if ( i == rope_x_lenght/SIZE)
                pthread_mutex_unlock(&rope_spacer);
            pthread_mutex_lock(&output_mutex);

            print_right(stage, id);
            refresh();

            pthread_mutex_unlock(&output_mutex);
            usleep(rope_x_time);
        }
        pthread_mutex_lock(&output_mutex);

        print_erase(rope_x_end);
        refresh();

        wscrl(output_log_win, 1);
        mvwprintw(output_log_win, LOG-1, 1,"%6d left->right finished\t( %ld in rope )", id, rope_count);
        wrefresh(output_log_win);

        rope_count--;
        pthread_mutex_unlock(&output_mutex);
    } else {
        // Start
        pthread_mutex_lock(&output_mutex);
        rope_count++;
        wait_count[type]--;
        print_right_counter();
        refresh();

        wscrl(output_log_win, 1);
        mvwprintw(output_log_win, LOG-1, 1,"%6d right->left start\t( %ld in rope )", id, rope_count);
        wrefresh(output_log_win);
        pthread_mutex_unlock(&output_mutex);

        pthread_mutex_lock(&rope_spacer);
        // Transation
        for(i=0, stage=rope_x_end; stage>=rope_x_start; i++, stage--){
            if ( i == rope_x_lenght/SIZE)
                pthread_mutex_unlock(&rope_spacer);
            pthread_mutex_lock(&output_mutex);

            print_left(stage, id);
            refresh();

            pthread_mutex_unlock(&output_mutex);
            usleep(rope_x_time);
        }
        pthread_mutex_lock(&output_mutex);

        print_erase(rope_x_start);
        refresh();

        wscrl(output_log_win, 1);
        mvwprintw(output_log_win, LOG-1, 1,"%6d right->left finished\t( %ld in rope )", id, rope_count);
        wrefresh(output_log_win);

        rope_count--;
        pthread_mutex_unlock(&output_mutex);
    }
}

void print_land_right() {
    int row,col;
    int r, c;
    getmaxyx(stdscr,row,col);
    c = col - LAND + 1;
    r = row - 19;
    mvprintw(r++, c, "            .-***-.     ");
    mvprintw(r++, c, "          _/-=-.   \\    ");
    mvprintw(r++, c, "         (_|a a/   |_   ");
    mvprintw(r++, c, "          / *  \\   ,_)  ");
    mvprintw(r++, c, "     _    \\`=' /__/     ");
    mvprintw(r++, c, "    / \\_  .;--'  `-.    ");
    mvprintw(r++, c, "    \\___)//      ,  \\   ");
    mvprintw(r++, c, "     \\ \\/;        \\  \\  ");
    mvprintw(r++, c, "      \\_.|         | |  ");
    mvprintw(r++, c, "       .-\\ '     _/_/   ");
    mvprintw(r++, c, "     .'  _;.    (_  \\   ");
    mvprintw(r++, c, "    /  .'   `\\   \\\\_/   ");
    mvprintw(r++, c, "   |_ /       |  |\\\\    ");
    mvprintw(r++, c, "  /  _)       /  / ||   ");
    mvprintw(r++, c, " /  /       _/  /  //   ");
    mvprintw(r++, c, " \\_/       ( `-/  ||    ");
    mvprintw(r++, c, "           /  /   \\\\ .-.");
    mvprintw(r++, c, "           \\_/     \\'-'/");
    mvprintw(r++, c, "                    `*` ");
}

void print_land_left() {
    int row,col;
    int r, c;
    getmaxyx(stdscr,row,col);
    c = 0;
    r = row - 19;
    mvprintw(r++, c, "     .-***-.            ");
    mvprintw(r++, c, "    /   .-=-\\_          ");
    mvprintw(r++, c, "   _|   \\a a|_)         ");
    mvprintw(r++, c, "  (_,   /  * \\          ");
    mvprintw(r++, c, "     \\__\\ '='/    _     ");
    mvprintw(r++, c, "    .-'  '--;.  _/ \\    ");
    mvprintw(r++, c, "   /  ,      \\\\(___/    ");
    mvprintw(r++, c, "  /  /        ;\\/ /     ");
    mvprintw(r++, c, "  | |         |._/      ");
    mvprintw(r++, c, "   \\_\\_     ' /-.       ");
    mvprintw(r++, c, "   /  _)    .;_  '.     ");
    mvprintw(r++, c, "   \\_//   /'   '.  \\    ");
    mvprintw(r++, c, "    //|  |       \\ _|   ");
    mvprintw(r++, c, "   || \\  \\       (_  \\  ");
    mvprintw(r++, c, "   \\\\  \\  \\_       \\  \\ ");
    mvprintw(r++, c, "    ||  \\-' )       \\_/ ");
    mvprintw(r++, c, ".-. //   \\  \\           ");
    mvprintw(r++, c, "\\'-'/     \\_/           ");
    mvprintw(r++, c, " '*'                    ");
}

void print_init(){
    int row,col;
    int i, lenght;

    pthread_mutex_init(&output_mutex, NULL);
    pthread_mutex_init(&rope_spacer, NULL);
    pthread_mutex_lock(&output_mutex);

    rope_count = 0;
    wait_count[0] = 0;
    wait_count[1] = 0;

    initscr();          /* Start curses mode        */
    raw();              /* Line buffering disabled  */
    noecho();           /* Don't echo() while we do getch */

    getmaxyx(stdscr,row,col);       /* get the number of rows and columns */

    { //Rope
        rope_x_end = col - LAND - 24;
        rope_x_lenght = rope_x_end - rope_x_start + 1;
        rope_x_time = TIME / rope_x_lenght;
        move(ROPE_ROW, rope_x_start);
        for(i=rope_x_start; i<= col - LAND; i++)
            addch('=');
    }

    { //Log Win
        output_log_win = newwin(LOG, col - 2 * LAND, row - LOG, LAND);
        mvprintw(row - LOG - 1, LAND, "-LOG");
        for(i=rope_x_start + 4; i<= col - LAND; i++)
            addch('-');
        scrollok (output_log_win, TRUE);
        wrefresh(output_log_win);
    }

    { //Borders
        lenght = col - LAND;
        for(i=0; i<row; i++) {
            mvaddch(i, LAND-1, '|');
            mvaddch(i, lenght, '|');
        }
    }

    print_land_right(); // Right Side
    print_right_counter();
    print_land_left();  // Left Side
    print_left_counter();

    refresh();

    pthread_mutex_unlock(&output_mutex);
}

void print_wait(){
    getch();
    endwin();           /* End curses mode        */
}
